docs: clarify Service.interface(), VarlinkError, RequestHandler, and server classes#87
docs: clarify Service.interface(), VarlinkError, RequestHandler, and server classes#87
Conversation
There was a problem hiding this comment.
Pull request overview
Documentation-focused PR to reduce common user confusion around implementing varlink services/servers and raising custom VarlinkErrors.
Changes:
- Clarifies
Service.interface()usage and how.varlinkfiles are located/loaded. - Adds guidance on wiring
Serviceinto aRequestHandlersubclass and running servers. - Expands docs for
VarlinkErrorsubclasses and adds docstrings forThreadingServer/ForkingServer; updates package-level server guidance.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
varlink/server.py |
Enhances pydoc for Service.interface(), RequestHandler, and server classes; adds examples and server-class docstrings. |
varlink/error.py |
Expands VarlinkError docstring with guidance for user-defined errors. |
varlink/__init__.py |
Updates module docstring to explain recommended server setup and enumerates server classes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| specified in the constructor of the Service. It has to end in '.varlink'. | ||
| The methods defined on the decorated class directly implement the varlink | ||
| interface methods. Each method receives the varlink call parameters as | ||
| keyword arguments and must return a dict matching the method's return type. |
There was a problem hiding this comment.
The docstring says handler methods receive varlink call parameters as keyword arguments, but Service._handle currently invokes methods with positional arguments (in the order of method.in_type.fields) and only uses keyword args for the special _raw/_message/_more/etc parameters. This wording is misleading; update it to describe positional args (and optionally mention the special keyword-only parameters).
| keyword arguments and must return a dict matching the method's return type. | |
| positional arguments (in the order of the method's input fields) and must | |
| return a dict matching the method's return type. Additional information may | |
| be passed via special keyword-only parameters (such as ``_raw``, ``_message``, | |
| or ``_more``), if supported by the Service implementation. |
| >>> class ServiceRequestHandler(varlink.RequestHandler): | ||
| >>> service = service | ||
| >>> | ||
| >>> server = varlink.ThreadingServer("unix:@example", ServiceRequestHandler) |
There was a problem hiding this comment.
The example mixes unqualified Service(...) with varlink.RequestHandler/varlink.ThreadingServer, but it never shows import varlink (or from varlink import Service). In a typical import varlink usage, Service won’t be in scope, so the snippet is not directly runnable as written. Consider either fully-qualifying the Service construction (varlink.Service(...)) or dropping the varlink. prefixes and keeping everything module-local.
| >>> class ServiceRequestHandler(varlink.RequestHandler): | |
| >>> service = service | |
| >>> | |
| >>> server = varlink.ThreadingServer("unix:@example", ServiceRequestHandler) | |
| >>> class ServiceRequestHandler(RequestHandler): | |
| >>> service = service | |
| >>> | |
| >>> server = ThreadingServer("unix:@example", ServiceRequestHandler) |
| :param filename: A varlink interface name (e.g. ``'com.example.service'``). | ||
| The file ``{filename}.varlink`` is loaded from the ``interface_dir`` given | ||
| to the Service constructor. This must be an interface name or a file path, | ||
| **not** a Python class or type. |
There was a problem hiding this comment.
Service.interface() docstring says filename can be an “interface name or a file path”, but the implementation treats non-absolute values as interface names: it always appends .varlink and prefixes interface_dir. Only absolute paths are opened as-is. Clarify this behavior (and whether callers should pass an absolute path to a .varlink file when using a path).
| :param filename: A varlink interface name (e.g. ``'com.example.service'``). | |
| The file ``{filename}.varlink`` is loaded from the ``interface_dir`` given | |
| to the Service constructor. This must be an interface name or a file path, | |
| **not** a Python class or type. | |
| :param filename: Either (1) a varlink interface name (e.g. | |
| ``'com.example.service'``), in which case the file | |
| ``{filename}.varlink`` is loaded from the ``interface_dir`` given to the | |
| Service constructor, or (2) an absolute path to a ``.varlink`` file, | |
| which is opened directly. Any non-absolute value is treated as an | |
| interface name (with ``.varlink`` appended and ``interface_dir`` | |
| prepended), so callers who want to load by path must pass an absolute | |
| path. This must be an interface name or a file path, **not** a Python | |
| class or type. |
| ``@service.interface()`` decorator, wire it to a :class:`varlink.RequestHandler` subclass, and run it | ||
| with one of the server classes: | ||
|
|
||
| - :class:`varlink.Server` -- single-connection base server (analogous to ``socketserver.TCPServer``) |
There was a problem hiding this comment.
The module docstring describes varlink.Server as a “single-connection base server”, but Server (like socketserver.TCPServer) can accept multiple connections sequentially; it’s single-threaded / non-concurrent rather than single-connection. Rewording would avoid implying it only handles one connection total.
| - :class:`varlink.Server` -- single-connection base server (analogous to ``socketserver.TCPServer``) | |
| - :class:`varlink.Server` -- single-threaded base server for sequential connections (analogous to ``socketserver.TCPServer``) |
varlink/server.py
Outdated
| Import from the ``varlink`` package (``varlink.ForkingServer``), **not** from | ||
| ``socketserver``. |
There was a problem hiding this comment.
FWIW I find this and similar sentences in the patch unclear. I think it's trying to say that other components of varlink library expect this class and not the somewhat similarly named class in socketserver correct? I think it could be worded better in that case, especially since there's no ForkingServer in socketserver (the closest are ForkingTCPServer and other ForkingServer classes - AFAICT).
There was a problem hiding this comment.
Maybe it's me, but this part doesn't look changed. I also don't feel strongly about it and was just providing feedback.
behrmann
left a comment
There was a problem hiding this comment.
Nice docs improvement, just two minor comments.
|
|
||
| class VarlinkError(Exception): | ||
| """The base class for varlink error exceptions""" | ||
| """The base class for varlink error exceptions. |
There was a problem hiding this comment.
A small nit: I think in the Python world it's clear that errors are modelled as exceptions, so this is a bit doubled
| """The base class for varlink error exceptions. | |
| """The base class for varlink errors. |
| # Given: error ActionFailed (field: string) in the .varlink file | ||
|
|
||
| class ActionFailed(VarlinkError): | ||
| def __init__(self, reason): | ||
| VarlinkError.__init__(self, { | ||
| "error": "com.example.ActionFailed", | ||
| "parameters": {"field": reason}, | ||
| }) |
There was a problem hiding this comment.
I think we can spell this out more clearly:
| # Given: error ActionFailed (field: string) in the .varlink file | |
| class ActionFailed(VarlinkError): | |
| def __init__(self, reason): | |
| VarlinkError.__init__(self, { | |
| "error": "com.example.ActionFailed", | |
| "parameters": {"field": reason}, | |
| }) | |
| # Given: error ActionFailed (field: string) in an org.example.myinterface.varlink file | |
| class ActionFailed(VarlinkError): | |
| def __init__(self, reason): | |
| VarlinkError.__init__(self, { | |
| "error": "org.example.myinterface.ActionFailed", | |
| "parameters": {"field": reason}, | |
| }) |
Also changing com.example to org.example, because it's the more common combination, I think.
| def interface(self, filename): | ||
| """Decorator that registers a class as the handler for a varlink interface. | ||
|
|
||
| :param filename: A varlink interface name (e.g. ``'com.example.service'``). |
There was a problem hiding this comment.
I'd go here and below with org as well.
67483da to
263f0bd
Compare
263f0bd to
37d0a02
Compare
…server classes
Improve pydoc documentation to address common points of confusion:
- Service.interface() expects a varlink interface name that maps to a
.varlink file in interface_dir, not a Python class
- VarlinkError subclasses for user-defined errors require manually
constructing the {"error": ..., "parameters": {...}} dict
- RequestHandler requires a `service` class variable wired to the
Service instance
- ThreadingServer and ForkingServer live in the varlink package, not
socketserver; add docstrings and list all server classes in the
module docstring
37d0a02 to
a64ff80
Compare
Improve pydoc documentation to address common points of confusion:
serviceclass variable wired to the Service instance